using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Collections;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Data;
using umbraco;
using umbraco.BusinessLogic;
using System.Web;
using umbraco.BusinessLogic.Actions;
using umbraco.DataLayer;
using umbraco.cms.businesslogic;
using umbraco.interfaces;
using System.Xml.XPath;
using umbraco.BasePages;

namespace umbraco.cms.presentation.user
{
    /// <summary>
    /// Provides umbraco user permission functionality on various nodes. Only nodes that are published are queried via the cache.
    /// </summary>
    /// <remarks>
    /// Uses the Umbraco API for the majority of operations, however, there is 1 manual SQL operation performed since using the Umbraco API to perform these
    /// tasks would have been very database intensive. Also, there are quite a few XPath queries on the Umbraco cache file since using the Umbraco API to look up
    /// these values would also have been very database intensive.
    /// </remarks>
    public class UserPermissions
    {

        User m_user;

        public UserPermissions(User user)
        {
            m_user = user;
        }

        private static ISqlHelper SqlHelper
        {
            get { return Application.SqlHelper; }
        }

        /// <summary>
        /// saves new permissions with the parameters supplied
        /// </summary>
        /// <param name="nodeIDs"></param>
        /// <param name="actions"></param>
        /// <param name="replaceChildren"></param>
        public void SaveNewPermissions(int[] nodeIDs, List<umbraco.interfaces.IAction> actions, bool replaceChildren)
        {
            //ensure permissions that are permission assignable
            List<IAction> permissions = actions.FindAll(
                delegate(IAction a)
                {
                    return (a.CanBePermissionAssigned);
                }
            );

            //ensure that only the nodes that the user has permissions to update are updated
            List<int> lstNoPermissions = new List<int>();           
            foreach (int nodeID in nodeIDs)
            {
                string nodeActions = UmbracoEnsuredPage.CurrentUser.GetPermissions(GetNodePath(nodeID));
                List<IAction> lstActions = Action.FromString(nodeActions);
                if (lstActions == null || !lstActions.Contains(ActionRights.Instance))
                    lstNoPermissions.Add(nodeID);
            }
            //remove the nodes that the user doesn't have permission to update
            List<int> lstNodeIDs = new List<int>();
            lstNodeIDs.AddRange(nodeIDs);
            foreach (int noPermission in lstNoPermissions)
                lstNodeIDs.Remove(noPermission);
            nodeIDs = lstNodeIDs.ToArray();

            //get the complete list of node ids that this change will affect
            List<int> allNodes = new List<int>();
            if (replaceChildren)
                foreach (int nodeID in nodeIDs)
                    allNodes.AddRange(FindChildNodes(m_user.Id, nodeID));                    
            else
                allNodes.AddRange(nodeIDs);

            //First remove all permissions for all nodes in question       
            DeletePermissions(m_user.Id, allNodes.ToArray());

            //if permissions are to be assigned, then assign them
            if (permissions.Count > 0)
                foreach (umbraco.interfaces.IAction oPer in permissions)
                    InsertPermissions(allNodes.ToArray(), oPer);
            else
            {
                //If there are NO permissions for this node, we need to assign the ActionNull permission otherwise
                //the node will inherit from it's parent.
                InsertPermissions(nodeIDs, ActionNull.Instance);
            }                

            //clear umbraco cache (this is the exact syntax umbraco uses... which should be a public method).
            HttpRuntime.Cache.Remove(string.Format("UmbracoUser{0}", m_user.Id.ToString()));
            //TODO:can also set a user property which will flush the cache!
        }

        /// <summary>
        /// Returns the current user permissions for the node specified
        /// </summary>
        /// <param name="nodeID"></param>
        /// <returns></returns>
        public List<IAction> GetExistingNodePermission(int nodeID)
        {
            string path = GetNodePath(nodeID);
            if (path != "")
            {
                //get the user and their permissions
                string permissions = m_user.GetPermissions(path);
                return Action.FromString(permissions);
            }
            return null;
        }

        /// <summary>
        /// gets path attribute for node id passed
        /// </summary>
        /// <param name="iNodeID"></param>
        /// <returns></returns>
        private string GetNodePath(int iNodeID)
        {
            string nodePath = "";
            string xPath = "//node[@id=" + iNodeID.ToString() + "]";
            XPathNodeIterator node = library.GetXmlNodeByXPath(xPath);
            if (node.MoveNext())
                nodePath = node.Current.GetAttribute("path", "");
            return nodePath;
        }

        /// <summary>
        /// Finds all child node IDs
        /// </summary>
        /// <param name="userID"></param>
        /// <param name="nodeID"></param>
        /// <returns></returns>
        private List<int> FindChildNodes(int userID, int nodeID)
        {
            XPathNodeIterator node = library.GetXmlNodeByXPath("//node[@id=" + nodeID.ToString() + "]");
            List<int> foundNodes = new List<int>();
            
            //add the current node
            foundNodes.Add(nodeID);
            
            //add all child nodes
            if (node.MoveNext())
                foundNodes.AddRange(FindChildNodes(node.Current));

            return foundNodes;
        }

        /// <summary>
        /// returns all node ids of the children of the node passed in
        /// </summary>
        /// <param name="xNav"></param>
        /// <returns></returns>
        private List<int> FindChildNodes(XPathNavigator xNav)
        {
            List<int> foundNodes = new List<int>();
            if (xNav.HasChildren)
            {
                XPathNodeIterator children = xNav.SelectChildren("node", "");
                while (children.MoveNext())
                {
                    foundNodes.Add(int.Parse(children.Current.GetAttribute("id", "")));
                    if (children.Current.HasChildren)
                        foundNodes.AddRange(FindChildNodes(children.Current));
                }
            }

            return foundNodes;
        }

        private void InsertPermissions(int[] nodeIDs, IAction permission)
        {
            foreach (int i in nodeIDs)
                InsertPermission(i, permission);
        }

        private void InsertPermission(int nodeID, IAction permission)
        {            
            //create a new CMSNode object but don't initialize (this prevents a db query)
            CMSNode node = new CMSNode(nodeID, false);
            Permission.MakeNew(m_user, node, permission.Letter);         
        }
   
        private static void DeletePermissions(int iUserID, int iNodeID)
        {
            DeletePermissions(iUserID, new int[] { iNodeID });
        }

        private static void DeletePermissions(int iUserID, int[] iNodeIDs)
        {
            string sql = "DELETE FROM umbracoUser2NodePermission WHERE nodeID IN ({0}) AND userID=@userID";
            string nodeIDs = string.Join(",", Array.ConvertAll<int, string>(iNodeIDs, Converter));
            sql = string.Format(sql, nodeIDs);
            SqlHelper.ExecuteNonQuery(sql,
                new IParameter[] { SqlHelper.CreateParameter("@userID", iUserID) });
        }

        private static string Converter(int from)
        {
            return from.ToString();
        }
        
    }
}